# coding=utf-8
"""
作者：董新强
创建时间：2020-3-12
描述：能力测试(假设曝光度为1的都被却掉了,因为这样的题没用处)
"""
from datetime import datetime

import numpy
from sqlalchemy import desc, func

from db_models.apps.app_cat_answers import AppCatAnswers as Answer
from db_models.apps.app_cat_test import AppCatTest as Test
from db_models.apps.app_cat_test_logs import AppCatTestLogs as  TestLogs
from db_models.cat_abilities import CatAbilities
from db_models.cat_questions import CatQuestions
from framework.db_session import DbSession
from framework.log_util import trycatch
from framework.msg import failure, ok
from framework.utilities import to_int
from services.applications.answer_model import AnswerModel
from services.cat_service.evaluator import Evaluator
from services.cat_service.question_bank import QuestionBank
from services.cat_service.selector import Selector
from services.cat_service.stoper import Stoper
from setting import MAX_TEST_ITEM_NUM


class AnswerManage():
    def __init__(self, appid, test_id, init_theta=0.5, max_exposure_rate=1, selector='difficult', pre_answers=None):
        self.appid = appid
        self.test_id = test_id
        self._selector = selector
        self._stop_num = MAX_TEST_ITEM_NUM

        self.init_theta = init_theta  # 初始能力值
        self.max_exposure_rate = max_exposure_rate  # 曝光度
        self._pre_answers = pre_answers

    @trycatch(failure(msg='答题失败'))
    def to_answer(self, reqs: dict, is_test=False):
        """答题"""
        qid, answer = to_int(reqs.get('qid')), reqs.get('answer')
        if not qid or answer is None: return failure(msg='参数错误')

        with DbSession.create() as db:

            # 确定是否已经答过改题目
            test = db.query(Test).filter(Test.appid == self.appid).filter(Test.id == self.test_id).first()

            if not test:
                return failure(msg='测试不存在')

            if test.status:  # 测试结束
                return ok(data=AnswerModel(finished=1, score=test.score, msg='完成测试').json())

            answer_ls, qid_ls, theta_ls = self.get_pre_answers(db, test_id=test.id)

            if qid in qid_ls:  # 已经答过的话,选择新的题目
                return self.next_question(test, qid_ls, theta_ls, answer_ls, db)

            # 获取答题的theta
            std_answer = db.query(CatQuestions.answer).filter(CatQuestions.id == qid).scalar()

            # 添加答案已经问题ID到列表里
            # 答案列表
            answer_ls.append(answer if is_test else std_answer == answer.lower())

            # 选择的问题ID的列表
            qid_ls.append(qid)

            # 选择题的参数的列表
            selected_items_params_ls = QuestionBank.get_item_params(test.aid, qid_ls)

            # theta的列表
            theta_ls.append(float(Evaluator.estimate(answer_ls, selected_items_params_ls)))  # 计算theta

            a = Answer()
            a.qid = qid
            a.answer = answer_ls[-1]
            a.test_id = test.id
            a.theta = theta_ls[-1]
            db.add(a)

            if Stoper(theta_ls, selected_items_params_ls, answer_ls, max_item_num=self._stop_num).stop:
                return ok(data=AnswerModel(finished=1, score=self._set_test_to_finished(answer_ls, db, test, theta_ls), msg='完成测试').json())  # 测试结束

            return self.next_question(test, qid_ls, theta_ls, answer_ls, db)

    def next_question(self, test, qid_ls, theta_ls, answer_ls, db):
        # 选择新的题目
        selected_qid = self._select_new_item(test.aid, qid_ls, theta_ls)
        if not selected_qid:  # 没有题目了,测试就结束了
            return ok(data=AnswerModel(finished=1, score=self._set_test_to_finished(answer_ls, db, test, theta_ls), msg='完成测试').json())

        # 如果项目还没结束
        msg = '回答正确' if answer_ls[-1] else '回答错误'
        return ok(data=AnswerModel(qid=selected_qid, msg=msg, dic_others={'answers': self._pre_answers}).json())

    def _set_test_to_finished(self, answer_ls, db, test, theta_ls):
        """
        设置测试为完成状态
        @param answer_ls:答案列表
        @param db: 数据库连接
        @param test: 测试
        @param theta_ls: 能力列表
        @return: 得分
        """
        # 设置测试已完成-----------------------
        score = AnswerManage._fina_theta(theta_ls, answer_ls, test.aid, db)
        score = max(min(int(score * 100), 100), 0)
        test.score = score
        test.status = True  # 测试完成
        test.end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # 日志记录
        test_log = TestLogs()
        test_log.test_id = test.id
        test_log.aid = test.aid
        db.add(test_log)

        # ----------------------------------------------
        return score

    @staticmethod
    def _fina_theta(theta_ls, answer_ls, aid, db):
        """
        最终theta(得分)
        @param theta_ls: 能力列表
        @param answer_ls: 答案列表
        @param aid: 能力ID
        @param db: 数据库连接
        @return: 如果预测能力>0,才用计算出来的theta,但如果theta比最低难度系数还低,就返回 theta和最低难度系数 * 0.5 两者最大的得分
                 否则,预测能力为0分:按每正确一题的1分计算,得分等于=min[ sum(answer)/count(answer) , 最小难度系数*0.5 ]
                 这样,也可以保证预测的分数大于直接得零分的人是得分,也保证最终得分不为0
        """
        if not theta_ls:
            return 0

        min_b = db.query(func.min(CatQuestions.b)).filter(CatQuestions.aid == aid).scalar()  # 最低难度系数

        score = (numpy.mean(theta_ls[-4:]) + 3) / 6.0  # 归一化处理
        if score > 0:
            # 如果预测能力>0,才用计算出来的theta,但如果theta比最低难度系数还低,就返回最低难度系数,否则也太寒碜了吧
            return score if score > min_b else max(score, min_b * 0.5)  # 保证得分不低于0.5倍难度系数

        # 如果用户得分为零的情况,可能是他的得分真的为零,也可能是因为题目有问题,
        # 总之一句话,不应该让他的得分为零
        # 但他的得分应该不高于该套试卷最低的难度系数
        score = 0.0
        for a in answer_ls:
            if a:
                score += 1.0
        return max(0, min(score / len(answer_ls), min_b * 0.5))  # 保证得不高于 0.5倍难度系数

    def start_test(self, reqs):
        """开始测试"""
        uid, aid = to_int(reqs.get('uid')), to_int(reqs.get('aid'))
        if not uid or not aid: return failure(msg='缺少用户ID')

        with DbSession.create() as db:
            ability = db.query(CatAbilities.id).filter(CatAbilities.appid == self.appid).filter(CatAbilities.status == 1).filter(CatAbilities.id == aid).first()
            if not ability: return failure(msg='能力不存在')

            answer_ls, qid_ls, theta_ls = [], [], []
            test = Test()
            test.aid = aid
            test.appid = self.appid
            test.uid = uid
            test.status = False
            test.start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            test.score = 0
            db.add(test)
            db.flush()
            selected_qid, test_id = self._select_new_item(aid, qid_ls, theta_ls), test.id

            return ok(data={'tid': test_id, 'qid': selected_qid})

    def _select_new_item(self, aid, qid_ls, theta_ls) -> int:
        if self._selector == 'difficult':
            qid = Selector.select_item_use_difficult(aid, self.init_theta, theta_ls, qid_ls)
        else:  # 默认为最大信息量
            qid = Selector.select_item_use_max_info(aid, self.init_theta, theta_ls, qid_ls, self.max_exposure_rate)

        if not qid: return 0

        return qid

    def get_pre_answers(self, db, test_id):
        if self._pre_answers: return self._pre_answers

        qid_ls, answer_ls, theta_ls = [], [], []
        query = db.query(Answer.qid, Answer.answer, Answer.theta) \
            .filter(Answer.test_id == test_id) \
            .order_by(desc(Answer.id)) \
            .limit(self._stop_num)

        for qid, answer, theta in query:
            answer_ls.insert(0, answer)
            qid_ls.insert(0, qid)
            theta_ls.insert(0, theta)
        self._pre_answers = [answer_ls, qid_ls, theta_ls]
        return self._pre_answers
